package com.skivingcloud.admin.logutils;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import com.skivingcloud.admin.constants.Constant;
import com.skivingcloud.admin.security.SecurityUser;
import com.skivingcloud.admin.security.UserUtil;
import com.skivingcloud.admin.sys.entity.SysLog;
import com.skivingcloud.admin.sys.service.SysLogService;
import com.skivingcloud.common.utils.ReturnT;
import com.skivingcloud.common.utils.UuidUtil;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;

/**
 * 日志切面
 * @author hushouquan
 */
@Aspect
@Component
public class OperateLogAspect{
    private static final Logger log = LoggerFactory.getLogger(OperateLogAspect.class);
    private static final ObjectMapper mapper = new ObjectMapper();
    static{
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }
    public static final String UNKNOWN_IP_STR = "unknown";
    private LogSpelProcess logSpelProcess;
    private SysLogService sysLogService;

    public OperateLogAspect(LogSpelProcess logSpelProcess, SysLogService sysLogService) {
        this.logSpelProcess = logSpelProcess;
        this.sysLogService = sysLogService;
    }

    /**
     * 定义切点
     */
    @Pointcut("@annotation(com.skivingcloud.admin.logutils.OperateLog)")
    public void pointcutLog() {
    }

    /**
     * 在原方法成功执行之后，再调用此通知。
     *
     * @param joinPoint 切面参数
     */
    @Around("pointcutLog()")
    public Object afterReturning(ProceedingJoinPoint joinPoint) throws Throwable {
        return insertLog(joinPoint);
    }

    /**
     * 进行插入日志
     *
     * @param joinPoint 切面参数
     */
    private Object insertLog(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取切点方法上的注解
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        OperateLog annotation = method.getAnnotation(OperateLog.class);
        SysLog sysLog = this.recordLog(annotation, joinPoint);
        Object[] methodParam = joinPoint.getArgs();
        sysLog.setMethods(joinPoint.getThis().getClass().getName() + method.getName());
        //参数
        sysLog.setOperateParams(mapper.writeValueAsString(methodParam));
        // 执行方法所消耗的时间
        long start = System.currentTimeMillis();
        //如果是系统异常那就直接抛出异常也不需要记录日志，但如果是业务异常，那就用记录这个日志是否成功
        Object proceed = joinPoint.proceed();
        long end = System.currentTimeMillis();
        long time = end - start;
        sysLog.setActionTime(Long.toString(time));
        sysLog.setId(UuidUtil.get32UUID());
        String jsonStr = mapper.writeValueAsString(proceed);
        JsonNode node = mapper.readTree(jsonStr);
        Integer code = node.has("code") ? node.get("code").asInt() : null;
        String message = node.has("message") ? node.get("message").asText() : null;
        if (Objects.equals(ReturnT.SUCCESS_CODE, code)) {
            //执行成功
                sysLog.setStatus(Constant.COMMON_YES_OR_NO_YES);
        } else {
            sysLog.setStatus(Constant.COMMON_YES_OR_NO_NO);
            sysLog.setErrorMessage(message);
        }
        //请求结果
        sysLog.setResult(jsonStr);
        //异步存储，这里用默认线程池，也可以自定义线程池
        CompletableFuture.runAsync(() ->
                sysLogService.save(sysLog)
        );
        return proceed;
    }

    /**
     * 生成最终日志
     *
     * @param annotation 注解
     * @return joinPoint 切面
     */
    private SysLog recordLog(OperateLog annotation, ProceedingJoinPoint joinPoint){
        //获取存在Spel表达式的属性
        List<String> templates = Lists.newArrayList(annotation.module(), annotation.methods(), annotation.description(), annotation.bussid());
        templates = templates.stream().filter(StringUtils::isNotBlank).toList();
        //解析SPEL属性和方法
        Map<String, String> processMap = logSpelProcess.processBeforeExec(templates, joinPoint);
        //解析三目运算
        Map<String, String> process = logSpelProcess.ternaryProcess(processMap, joinPoint);
        SysLog sysLog = new SysLog();
        // 执行方法所消耗的时间
        sysLog.setBussid(process.get(annotation.bussid()));
        sysLog.setDescription(process.get(annotation.description()));
        sysLog.setMethods(annotation.methods());
        sysLog.setFunctionNo(annotation.functionNo());
        sysLog.setModule(annotation.module());
        sysLog.setOperTime(LocalDateTime.now());
        SecurityUser user = UserUtil.getCurrentUser();
        if(user != null){
            sysLog.setUserId(user.getId());
        }
        sysLog.setUserIp(getClientIp());
        return sysLog;
    }

    private String getClientIp(){
        String ip = null;
        try{
            HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest();
            ip = request.getHeader("x-forwarded-for");
            if (ip == null || ip.isEmpty() || UNKNOWN_IP_STR.equalsIgnoreCase(ip)) {
                ip = request.getHeader("Proxy-Client-IP");
            }
            if (ip == null || ip.isEmpty() || UNKNOWN_IP_STR.equalsIgnoreCase(ip)) {
                ip = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ip == null || ip.isEmpty() || UNKNOWN_IP_STR.equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_CLIENT_IP");
            }
            if (ip == null || ip.isEmpty() || UNKNOWN_IP_STR.equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_X_FORWARDED_FOR");
            }
            if (ip == null || ip.isEmpty() || UNKNOWN_IP_STR.equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddr();
            }
        }catch(IllegalStateException e){
            log.error(e.getMessage(), e);
        }
        return ip;
    }
}
